package cn.xeblog.server.action.handler;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.comparator.VersionComparator;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Singleton;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.xeblog.commons.constants.IpConstants;
import cn.xeblog.commons.entity.*;
import cn.xeblog.commons.enums.Action;
import cn.xeblog.commons.enums.MessageType;
import cn.xeblog.commons.enums.Platform;
import cn.xeblog.commons.util.CheckUtils;
import cn.xeblog.server.action.ChannelAction;
import cn.xeblog.server.action.specialAc.SpecialCommandHandler;
import cn.xeblog.server.action.timer.TokenBucket;
import cn.xeblog.server.action.timer.TokenBucketFactory;
import cn.xeblog.server.annotation.DoAction;
import cn.xeblog.server.builder.ResponseBuilder;
import cn.xeblog.server.cache.BlackUserCache;
import cn.xeblog.server.cache.RedisCache;
import cn.xeblog.server.cache.UserCache;
import cn.xeblog.server.config.GlobalConfig;
import cn.xeblog.server.config.ServerConfig;
import cn.xeblog.server.constant.CommonConstants;
import cn.xeblog.server.factory.ObjectFactory;
import cn.xeblog.server.service.AbstractResponseHistoryService;
import cn.xeblog.server.util.IpUtil;
import cn.xeblog.server.util.SensitiveWordUtils;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * @author anlingyi
 * @date 2020/8/14
 */
@Slf4j
@DoAction(Action.LOGIN)
public class LoginActionHandler implements ActionHandler<LoginDTO> {

    @Override
    public void handle(ChannelHandlerContext ctx, LoginDTO body) {
        if (ChannelAction.getUser(ctx) != null) {
            ctx.writeAndFlush(ResponseBuilder.system("请勿重复登录！"));
            return;
        }

        if (body.getPlatform() == null) {
            body.setPlatform(Platform.IDEA);
        }
        String pluginVersion = body.getPluginVersion();
        String[] split = pluginVersion.split(";");
        if (body.getPlatform() == Platform.IDEA) {
            // IDEA平台登录的需要比对插件版本
            String currentPluginVersion = CommonConstants.PLUGIN_VERSION;
            String userPluginVersion = split[0];
            if (StrUtil.isNotBlank(userPluginVersion)) {
                int len = currentPluginVersion.length();
                int len2 = userPluginVersion.length();
                int k = len - len2;
                String padding = "";
                for (int i = Math.abs(k); i > 0; i--) {
                    padding += "0";
                }
                if (k > 0) {
                    userPluginVersion += padding;
                } else if (k < 0) {
                    currentPluginVersion += padding;
                }
            }
            int versionState = VersionComparator.INSTANCE.compare(currentPluginVersion, userPluginVersion);
            if (versionState > 0) {
                ctx.writeAndFlush(ResponseBuilder.system("温馨提醒~ 请尽快更新插件版本至v" + CommonConstants.PLUGIN_VERSION + "！"));
                if (ServerConfig.getConfig().getCheckVersion()) {
                    ctx.close();
                    return;
                }
            }
            if (versionState < 0) {
                ctx.writeAndFlush(ResponseBuilder.system("当前服务端版本过低，请联系塘主更新服务端版本！你的版本：v" + pluginVersion + "，服务端版本：v" + CommonConstants.PLUGIN_VERSION));
                if (ServerConfig.getConfig().getCheckVersion()) {
                    ctx.close();
                    return;
                }
            }
        }

        boolean isReconnect = body.isReconnected();
        String username = body.getUsername();

        if (StrUtil.isBlank(username)) {
            ctx.writeAndFlush(ResponseBuilder.system("昵称不能为空！"));
            ctx.close();
            return;
        }

        if (!CheckUtils.checkUsername(username)) {
            ctx.writeAndFlush(ResponseBuilder.system("昵称不合法，请修改后重试！"));
            ctx.close();
            return;
        }

        if (username.length() > 12) {
            ctx.writeAndFlush(ResponseBuilder.system("昵称长度不能超过12个字符！"));
            ctx.close();
            return;
        }

        if (UserCache.existUsername(username)) {
            ctx.writeAndFlush(ResponseBuilder.system("[" + username + "]昵称重复！"));
            ctx.close();
            return;
        }

        if (SensitiveWordUtils.hasSensitiveWord(username)) {
            ctx.writeAndFlush(ResponseBuilder.system("昵称含有违规字符，请修改后重试！"));
            ctx.close();
            return;
        }

        String uuid = body.getUuid();
        if (StrUtil.isBlank(uuid)) {
            ctx.writeAndFlush(ResponseBuilder.system("未获取到UUID，请尝试重新登录！"));
            ctx.close();
            return;
        }

        // 校验黑名单
        if (BlackUserCache.checkInBlackList(1, username)) {
            ctx.writeAndFlush(ResponseBuilder.system("该用户名[" + username + "]已列入黑名单，不允许登录！请前往QQ群联系塘主。"));
            ctx.close();
            return;
        }
        final String ip = IpUtil.getIpByCtx(ctx);
        if (BlackUserCache.checkInBlackList(2, ip)) {
            ctx.writeAndFlush(ResponseBuilder.system("当前IP[" + ip + "]已列入黑名单，不允许登录！请前往QQ群联系塘主。"));
            ctx.close();
            return;
        }
        if (BlackUserCache.checkInBlackList(3, uuid)) {
            ctx.writeAndFlush(ResponseBuilder.system("您的UUID[" + uuid + "]已列入黑名单，不允许登录！请前往QQ群联系塘主。"));
            ctx.close();
            return;
        }

        if (checkBlackCache(username, ip, uuid)) {
            ctx.writeAndFlush(ResponseBuilder.system("您已进入黑名单！"));
            ctx.close();
            return;
        }

        String id = ChannelAction.getId(ctx);
        IpRegion ipRegion;
        if (split.length == 2) {
            ipRegion = new Gson().fromJson(split[1], IpRegion.class);
        } else {
            ipRegion = IpUtil.getRegionByIp(ip);
        }
        String configToken = ServerConfig.getConfig().getToken();
        boolean isAdmin = StrUtil.isNotBlank(configToken) && StrUtil.equals(configToken, body.getToken());
        if (isAdmin) {
            refreshCustomerAdmin();
        } else {
            isAdmin = isCustomerAdmin(uuid, body.getToken());
        }
        User user = new User(id, username, body.getStatus(), ip, ipRegion, ctx.channel());
        user.setUuid(uuid);
        user.setRole(isAdmin ? User.Role.ADMIN : User.Role.USER);
        user.setClientVersion(pluginVersion);
        user.setPlatform(body.getPlatform());
        user.setPermit(GlobalConfig.getUserPermit(user));
        user.setToken(body.getToken());
        UserCache.add(id, user);

        registerTokenBucket(id);

        ChannelAction.add(ctx.channel());
        ChannelAction.sendOnlineUsers(user);
        ChannelAction.sendUserState(user, UserStateMsgDTO.State.ONLINE);

        if (isReconnect) {
            user.send(ResponseBuilder.system("重新连接服务器成功！"));
        }
        user.send(ResponseBuilder.system("修身洁行，言必由绳墨。" + SpecialCommandHandler.getTips(isAdmin)));

        List<Response> historyMsgList = ObjectFactory.getObject(AbstractResponseHistoryService.class).getHistory(30);
        final String loginMsg = StrUtil.format("[{}·{}·{}]进入了鱼塘！",
                MapUtil.getStr(IpConstants.SHORT_PROVINCE, ipRegion.getProvince(), ipRegion.getCountry()),
                user.getUsername(), user.getPlatform().name());
        ChannelAction.send(ResponseBuilder.system(loginMsg));
        if (CollectionUtil.isNotEmpty(historyMsgList)) {
            user.send(ResponseBuilder.build(null, new HistoryMsgDTO(historyMsgList), MessageType.HISTORY_MSG));
        }
    }

    private boolean checkBlackCache(String username, String ip, String uuid) {
        RedisCache redisCache = Singleton.get(RedisCache.class);

        String userRegex = redisCache.hget(RedisCache.BLACK_LIST_CACHE, "userRegex");
        if (StrUtil.isNotEmpty(userRegex) && Pattern.compile(userRegex).matcher(username).matches()) {
            return true;
        }

        String ipRegex = redisCache.hget(RedisCache.BLACK_LIST_CACHE, "ipRegex");
        if (StrUtil.isNotEmpty(ipRegex) && Pattern.compile(ipRegex).matcher(ip).matches()) {
            return true;
        }

        String uuidRegex = redisCache.hget(RedisCache.BLACK_LIST_CACHE, "uuidRegex");
        return StrUtil.isNotEmpty(uuidRegex) && Pattern.compile(uuidRegex).matcher(uuid).matches();
    }

    private void registerTokenBucket(String id) {
        try {
            if (tokenBucket == null) {
                TokenBucketFactory.registerTokenBucket(new TokenBucket(id));
                return;
            }
            int maxCapacity = tokenBucket.getIntValue("maxCapacity");
            double tokensPerSecond = tokenBucket.getDoubleValue("tokensPerSecond");
            double initRate = tokenBucket.getDoubleValue("initRate");
            TokenBucketFactory.registerTokenBucket(new TokenBucket(id, maxCapacity, tokensPerSecond, initRate));
        } catch (Exception e) {
            log.error("初始化令牌桶出现异常！", e);
            TokenBucketFactory.registerTokenBucket(new TokenBucket(id));
        }
    }

    private final List<String> customerAdminKeys = new ArrayList<>();
    private final Map<String, String> userKeyMap = new HashMap<>();
    private JSONObject tokenBucket = null;

    private boolean isCustomerAdmin(String uuid, String bodyToken) {
        if (customerAdminKeys.contains(bodyToken)) {
            return true;
        }

        String tempToken = userKeyMap.get(uuid);
        return tempToken != null && tempToken.equals(bodyToken);
    }

    @SuppressWarnings("unchecked")
    private void refreshCustomerAdmin() {
        try {
            String json = FileUtil.readString("/home/xechat/config/config.json", StandardCharsets.UTF_8);
//            String json = FileUtil.readString("G:\\OpenSourceCode\\xechat-magic\\xechat-server\\src\\main\\resources\\config.json", StandardCharsets.UTF_8);
            JSONObject configObj = JSONObject.parseObject(json);

            JSONArray adminKeys = configObj.getJSONArray("adminKeys");
            customerAdminKeys.clear();
            adminKeys.forEach(k -> customerAdminKeys.add(k.toString()));

            userKeyMap.clear();

            JSONObject userKeys = configObj.getJSONObject("userKeys");
            userKeyMap.putAll(userKeys.toJavaObject(Map.class));

            tokenBucket = configObj.getJSONObject("tokenBucketKeys");
        } catch (Exception e) {
            log.info("临时权限解析异常！", e);
        }
    }

}
